`timescale 1ns / 1ps
//
// CSE141L Lab 2, Part 1: Fetch Datapath
// University of California, San Diego
//
// Written by Donghwan Jeon, 4/10/2007
// Updated by Sat Garcia, 4/9/2008
// Updated by Michael Taylor, 4/4/2011
// Modified by Tiffany Jaya A06919807, 4/13/2011

// Fetch unit module
//
// parameters:
//      I_WIDTH: instruction width
//      A_WIDTH: SRAM address width
//      O_WIDTH: offset width in a branch instruction (2's complement)
//

typedef struct packed {
 bit [17-1:0] instr;
 logic [10-1:0] addr;
} instr_packet_s;

module fetch#(parameter I_WIDTH = 17, A_WIDTH = 10, O_WIDTH = 5)
(
    input   clk,

    // inputs from exec unit
    input   dequeue_i,
    input   restart_i,
    input   [A_WIDTH-1: 0] restart_addr_i,
	 input	branch_i,
	 
    // memory interface
    input   load_store_valid_i,
    input   store_en_i,
    input   [A_WIDTH-1: 0] load_store_addr_i,
    input   [I_WIDTH-1: 0] store_data_i,
    output  [I_WIDTH-1 : 0] load_data_o,
    output  load_data_valid_o,
	 
    // ouputs to the exec unit
    output  [I_WIDTH-1: 0] instruction_data_o,
    output  [A_WIDTH-1: 0] instruction_addr_o,
    output  instruction_valid_o
);

	// control's wire
   reg                	fifo_enqueue, 
	                     fifo_clear,
								branch_r,
	                     ram_we;
   reg [2:0]	         sel_mux;
	
	// pc: program counter
   reg [A_WIDTH-1 : 0] pc_next; // wire?
	reg  [A_WIDTH-1 : 0] pc_r;
	
	// fifo's wire & registers
	wire						fifo_valid, 
								fifo_full, 
								fifo_empty,
								dequeue_w;
	
	// fifo's instr_packet_s
	instr_packet_s ip_in;
	instr_packet_s ip_out;	
	
	// sram's wire & registers
   reg [A_WIDTH-1 : 0] ram_addr; // output of mux3
   reg [I_WIDTH-1 : 0] ram_data;

	// adder's wire & registers
	wire[A_WIDTH-1:0] adderOffset_o;
	wire[A_WIDTH-1:0] framePointer_o;
	reg [A_WIDTH-1:0] adder1_output; // wire?
	
	// mux's wire & registers
	wire [A_WIDTH-1:0] mux00_output;
	wire [A_WIDTH-1:0] mux0_output;
	wire [A_WIDTH-1:0] mux1_output;
	wire [A_WIDTH-1:0] call_mux_output;
	
	// signext's wire & registers
	wire [A_WIDTH-1 : 0] offset;
	wire [A_WIDTH-1 : 0] ext_o;
	
   // input's wire & registers: data path inputs
   reg [A_WIDTH-1 : 0]  restart_addr_r;
   reg [A_WIDTH-1 : 0]  load_store_addr_r;
   reg [I_WIDTH-1 : 0]  store_data_r;

	// input's wire & registers: input control signals
	reg restart_r;
	reg load_store_valid_r;
   reg store_en_r;
	
	// output's wire & registers: output control signals
	reg instruction_valid_r;	
		
	reg load_data_valid_r;
	reg foo_next;
	reg foo_r;
    
   // validate inputs
   // synthesis translate off
   always_comb
     begin
        if (store_en_r & ~load_store_valid_r)
          $display("Warning: store_en_r set but load_store_valid_r not set!\n");
     end
   // synthesis translate on
	
   // fifo instantiation
   fifo fetch_fifo
     (
      .clk(clk)
      ,.instr_packet_i(ip_in)
      ,.deque_i(dequeue_i)
      ,.clear_i(fifo_clear)
      ,.enque_i(fifo_enqueue)
      ,.instr_packet_o(ip_out)
      ,.empty_o(fifo_empty)
      ,.full_o(fifo_full)
      ,.valid_o(fifo_valid)
      );


   // RAM instantiation
   ram_i ram
     (
      .address(pc_next)
      ,.clock(clk)
      ,.data(store_data_r)
      ,.wren(ram_we)
      ,.q(ram_data)             //data_out
      );

   // noble 141L student, complete this file!
   mux call_mux
		(
		 .sel(instruction_data_o[14])
		 ,.d0_i(instruction_data_o[9:0])
		 ,.d1_i(store_data_i[9:0])
		 ,.d_o(call_mux_output)
		);
	// mux instantiation
	mux mux00
		(
		 .sel(instruction_data_o[15])
		 ,.d0_i(ext_o)
		 ,.d1_i(call_mux_output)
		 ,.d_o(mux00_output)
		);
	
	// mux 0
	mux mux0
		(
		 .sel(sel_mux[0])
		 ,.d0_i(adderOffset_o)
		 ,.d1_i(adder1_output)
		 ,.d_o(mux0_output)
		);
		
	// mux 1
	mux mux1
		(
		 .sel(sel_mux[1])
		 ,.d0_i(pc_r)
		 ,.d1_i(mux0_output)
		 ,.d_o(mux1_output)
		);
		
	// mux 2
	mux mux2
		(
		 .sel(sel_mux[2])
		 ,.d0_i(restart_addr_r)
		 ,.d1_i(mux1_output)
		 ,.d_o(pc_next)
		);
		
	// mux 3
	/*mux mux3
		(
		 .sel(sel_mux[3])
		 ,.d0_i(pc_next)
		 ,.d1_i(load_store_addr_r)
		 ,.d_o(ram_addr)
		);*/
		
	// adder instantiation
	// adder +1
	adder adder1
		(
		 .d0_i(10'b0000000001)
		 ,.d1_i(pc_r)
		 ,.d_o(adder1_output)
		);
		
	signext ext
		(
		.d_i(instruction_data_o[5:0]),
		.d_o(ext_o)
		);
		
	// adder +offset
	adder adderOffset
		(
		 .d0_i(instruction_addr_o)
		 ,.d1_i(mux00_output)
		 ,.d_o(adderOffset_o)
		);
	
	adder framePointer
		(
		 .d0_i(instruction_addr_o),
		 .d1_i(store_data_i[A_WIDTH-1:0]),
		 .d_o(framePointer_o)
		);
		
	// flip-flop implementation
	always_ff @(posedge clk)
		begin
				restart_addr_r <= restart_addr_i;
				load_store_addr_r <= load_store_addr_i;
				store_data_r <= store_data_i;
				branch_r <= branch_i;
				
      		if(restart_i)
      		  pc_r <= restart_addr_i;
			  else
			    pc_r <= pc_next;
			    
		end
	
	// control implementation - linking registers ////////////////////////////

	
	// control implementation: input control signals /////////////////////////
	// 1. restart_i: 0: normal execution
	//            1: restart execution from 'restart_addr'
	// NOTE: sel_mux[2] = ~restart_r;
	// 2. load_store_valid_i: 0: no load or store operation
	//                     1: load or store operation
	// 3. store_en_i: 0: load
	//                1: store
	
	always_ff @(posedge clk)
	begin
		restart_r <= restart_i;
		load_store_valid_r <= load_store_valid_i;
		store_en_r <= store_en_i;
	end
	
	// control implementation: internal control signals /////////////////////

	// 1. sel_mux[0]: 0: Branch target address (i.e. PC+SignExt(offset))
	//                1: Next instruction (i.e. PC+1)			
	// 2. sel_mux[1]: 0: Previously used PC
	//                1: Output of mux0
	// 3. sel_mux[2]: 0: Restart address (i.e. restart_addr)
	//                1: Output of mux1
	// 4. sel_mux[3]: 0: Output of mux2
	//                1: Target address of load or store operation		
	// 5. ram_we: 0: Read data from SRAM
	//            1: Write data from SRAM
	// 6. fifo_enqueue: 0: No effect
	//                  1: Insert item into FIFO		
	// 7. fifo_clear: 0: No effect
	//                1: Remove all entries from the FIFO
	
	/*
	always_comb
	begin
		if(restart_i)
			begin
				sel_mux[0] = 1'b0;
				sel_mux[1] = 1'b0;
				sel_mux[2] = 1'b0;
				sel_mux[3] = 1'b0;
			end
		else
			begin
				sel_mux[0] = ~ram_data[16];
				sel_mux[1] = fifo_enqueue;
				sel_mux[2] = ~restart_r;
				sel_mux[3] = load_store_valid_r;
			end
	end
	*/

	assign sel_mux[0] = ~branch_r;

	assign sel_mux[1] = ~fifo_full || branch_r; //fifo_enqueue
	
	assign sel_mux[2] = ~restart_r;
	
	//assign sel_mux[3] = 1'b0;
	
	assign ram_we = 1'b0; //load_store_valid_r & store_en_r;

	//ARTHUR CHANGE! (OLD: ~(restart_r| load_store_valid_r))
	//assign fifo_enqueue = ~fifo_clear && sel_mux[1];
	assign fifo_enqueue = ~fifo_clear && sel_mux[1];
	assign fifo_clear = restart_r || branch_r;
	
	// control implementation: output control signals ///////////////////////
	// 1. instruction_valid_o: 0: Data on instruction_addr and instruction_data are invalid
	//                         1: instruction_addr and instruction_data are valid
   // 2. load_data_valid_o: 0: load_data is invalid
	//                       1: load_data is valid
		
	assign instruction_valid_o = instruction_valid_r;
	assign instruction_valid_r = ~fifo_empty & ~restart_r;
	
	always_ff @ (posedge clk)
	begin
		foo_r <= foo_next;
	end
	
	assign foo_next = load_store_valid_r & store_en_r;
	assign load_data_valid_r = foo_r;
	assign load_data_valid_o = load_data_valid_r;
	
	// extra stuff /////////////////////////////////////////////////////////
	// 1. control's output
	assign load_data_o = ram_data;
	
	// 2. fifo's input
	assign dequeue_w = fifo_empty ? 1'b0 : dequeue_i;
	
	// 3. fifo's output
	assign ip_in = '{ instr : ram_data, addr :  pc_r };
	assign { instruction_data_o, instruction_addr_o } = { ip_out.instr, ip_out.addr };

endmodule


























